<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
</body>
<script id="ProjectionVS" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    uniform mat4 u_LightPvM4;
    uniform mat4 u_ModelM4;
    void main(){
        gl_Position = u_LightPvM4 * u_ModelM4 * a_Position;
    }
</script>
<script id="ProjectionFS" type="x-shader/x-fragment">
    precision mediump float;
    void main(){
         const vec4 bitShift = vec4(
            1.0,
            256.0,
            256.0 * 256.0,
            256.0 * 256.0 * 256.0
        );
        const vec4 bitMask = vec4(vec3(1.0 / 256.0), 0.0);
        vec4 depth = fract(gl_FragCoord.z * bitShift);
        depth -= depth.gbaa * bitMask;
        gl_FragColor = depth;
    }
</script>
<script id="DepthVS" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    uniform mat4 u_ModelM4;
    uniform mat4 u_PvM4;
    void main(){
        gl_Position = u_PvM4 * u_ModelM4 * a_Position;
    }
</script>
<script id="DepthFS" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec4 u_Color;
    void main(){

        float depth = 1.0 - smoothstep(0.05, 0.95, gl_FragCoord.z);
        vec3 color = vec3(u_Color) * depth;
        gl_FragColor = vec4(color.rgb, 1.0);
    }
</script>
<script id="LightVS" type="x-shader/x-vertex">
    attribute vec4 a_Position;
    attribute vec3 a_Normal;
    uniform mat4 u_ModelM4;
    uniform mat4 u_PvM4;
    uniform mat4 u_LightPvM4;
    varying vec3 v_Position;
    varying vec3 v_Normal;
    varying vec4 v_ClipposLight;
    void main(){
        vec4 world = u_ModelM4 * a_Position;
        gl_Position = u_PvM4 * world;
        v_Position = vec3(world);
        v_Normal = a_Normal;
        v_ClipposLight = u_LightPvM4 * world;
    }
</script>
<script id="LightFS" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec4 u_Color;

    uniform float u_Kd;
    uniform float u_Ks;
    uniform float u_Ka;

    uniform vec3 u_LightEye;
    uniform vec3 u_LightLookAt;
    uniform float u_LightIntensity;
    uniform vec2 u_LightFov;
    uniform vec2 u_LightDist;
    uniform sampler2D u_ShadowMap;
    
    varying vec3 v_Normal;
    varying vec3 v_Position;
    varying vec4 v_ClipposLight;

    bool isInShadow(){
        vec3 fragPos = (v_ClipposLight.xyz / v_ClipposLight.w) / 2.0 + 0.5;
        vec4 shadowFrag = texture2D(u_ShadowMap, fragPos.xy);
        const vec4 bitShift = vec4(
            1.0,
            1.0 / (256.0),
            1.0 / (256.0 * 256.0),
            1.0 / (256.0 * 256.0 * 256.0)
        );
        float depth = dot(shadowFrag, bitShift);
        return fragPos.z > depth + 1.0 / (256.0 * 4.0);
    }

    void main(){
        vec3 normal = normalize(v_Normal);
        vec3 eyeDir = normalize(u_LightEye - v_Position);
        vec3 lightDir = normalize(u_LightEye - u_LightLookAt);
        vec3 h = normalize(eyeDir + lightDir);

        vec3 Ld = u_Kd * vec3(u_Color) * max(0.0, dot(normal, lightDir));
        vec3 Ls = u_Ks * vec3(1.0) * pow(max(0.0, dot(normal, h)), 64.0);
        vec3 La = u_Ka * vec3(u_Color);

        vec3 pl = u_LightEye - v_Position;
        float r = length(cross(pl, lightDir));
        float dist = dot(pl, lightDir);
        float fov1 = tan(u_LightFov.x) * dist;
        float fov2 = tan(u_LightFov.y) * dist;
        float fallX = 1.0 - smoothstep(fov1, fov2, r);
        float fallY = 1.0 - smoothstep(u_LightDist.x, u_LightDist.y, dist);
        float fallB = step(0.0, dist);

        float depth = 1.0 - smoothstep(0.05, 0.95, gl_FragCoord.z);
        float intensity = u_LightIntensity * fallY * fallX * fallB;
        float shadow = isInShadow() ? 0.3 : 1.0;
        vec3 color = ((Ld + Ls) * intensity * shadow + La) * depth;

        //限制一下最大亮度
        color.r = min(0.7, color.r);
        color.g = min(0.7, color.g);
        color.b = min(0.7, color.b);
        gl_FragColor = vec4(color, 1.0);
    }
</script>
<script type="module">
    import { Matrix4, Vector3, Color, OrthographicCamera, PerspectiveCamera, Spherical } from '../js/three.module.js';
    import { CreateProgram, imgPromise } from '../js/Utils.js';
    import lib from '../js/ThreeLib.js';
    import CameraController from '../js/CameraController.js'
    import Poly from '../js/Poly4.js'
    import Control from '../js/Control.js'
    import Backdrop from '../js/Light/Backdrop.js'
    import Sphere from '../js/objs/Sphere.js'
    import Square from '../js/objs/Square.js'

    import Geo from '../js/Poly2/Geo.js'
    import Mat from '../js/Poly2/Mat.js'
    import Obj3D from '../js/Poly2/Obj3D.js'
    import Scene from '../js/Poly2/Scene.js'
    import Frame from '../js/Poly2/Frame.js'

    //#region gl
    const canvas = document.getElementById("canvas");
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight - 4;

    const gl = canvas.getContext("webgl", { antialias: true, });

    // gl.enable(gl.BLEND);
    // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    // gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);//去图片描边
    // gl.enable(gl.DEPTH_TEST);
    //#endregion

    const scene = new Scene({ gl: gl });
    scene.addAxisProgram();

    scene.regProgram('light', {
        program: CreateProgram(gl,
            document.querySelector('#LightVS').innerText,
            document.querySelector('#LightFS').innerText),
        attribs: ['a_Position', 'a_Normal'],
        uniforms: [
            'u_PvM4', 'u_ModelM4', 'u_Color', 'u_ShadowMap', 'u_LightPvM4',
            'u_Kd', 'u_Ks', 'u_Ka', 'u_LightIntensity', 'u_LightDist', 'u_LightFov', 'u_LightEye', 'u_LightLookAt',
        ],
    });
    scene.regProgram('depth', {
        program: CreateProgram(gl,
            document.querySelector('#DepthVS').innerText,
            document.querySelector('#DepthFS').innerText),
        attribs: ['a_Position'],
        uniforms: [
            'u_PvM4', 'u_ModelM4', 'u_Color',
        ],
    });

    const frame = new Frame({ gl: gl });
    frame.regProgram('projection', {
        program: CreateProgram(gl,
            document.querySelector('#ProjectionVS').innerText,
            document.querySelector('#ProjectionFS').innerText),
        attribs: ['a_Position'],
        uniforms: [
            'u_LightPvM4', 'u_ModelM4',
        ],
    });

    const pvM4 = new Matrix4();
    const modelM4 = new Matrix4();
    const eye = new Vector3(3, 1, 3);
    const lookAt = new Vector3(0, 0, 0);

    const camera = new PerspectiveCamera(45, canvas.width / canvas.height, 1, 20);
    const cameraController = new CameraController({
        canvas,
        camera,
        pvMatrix: pvM4,
        eye,
        lookAt,
    });

    const Kd = 0.7;
    const Ks = 0.1;
    const Ka = 0.3;
    const lightIntensity = 30;
    const lightDist = [0, 20];
    const lightFov = [15 * Math.PI / 180, 25 * Math.PI / 180];
    const light = new PerspectiveCamera(60, canvas.width / canvas.height, 1, 20);
    light.position.copy(eye);
    light.lookAt(lookAt);
    light.updateMatrixWorld(true);
    const lightPvM4 = light.projectionMatrix.clone().multiply(light.matrixWorldInverse);


    //#region 
    const axis = new Obj3D().createAxis('axis');
    // scene.add(axis);

    const backdrop = new Backdrop({ is1: false });
    const [backdropVertices, backdropElements, backdropNormals] = [
        backdrop.vertices, backdrop.elements, backdrop.normals
    ];

    const sphere = new Sphere(0.5, 10, 10);
    const [sphereVertices, sphereElements, sphereNormals] = [
        sphere.vertices, sphere.elements, sphere.normals
    ];

    const square = new Square(0.5);
    const [squareVertices, squareElements, squareNormals] = [
        square.vertices, square.elements, square.normals
    ];


    const backdropTexture = createTexture(backdropVertices, backdropElements, new Matrix4());
    const sphereTexture = createTexture(sphereVertices, sphereElements, new Matrix4().setPosition(-1, 0, 1));
    const squareTexture = createTexture(squareVertices, squareElements, new Matrix4().setPosition(1, 0, -1));
    frame.add(backdropTexture)
    frame.add(sphereTexture)
    frame.add(squareTexture)
    frame.draw();

    const backdrop3D = createLight(backdropVertices, backdropElements, backdropNormals, frame.texture, new Matrix4(), [1, 1, 1, 1]);
    const sphere3D = createLight(sphereVertices, sphereElements, sphereNormals, frame.texture, new Matrix4().setPosition(-1, 0, 1), [0, 1, 1, 1]);
    const square3D = createLight(squareVertices, squareElements, squareNormals, frame.texture, new Matrix4().setPosition(1, 0, -1), [1, 0, 1, 1]);

    scene.add(backdrop3D);
    scene.add(sphere3D);
    scene.add(square3D);
    //#endregion

    function createLight(vertices, elements, normals, texture, mm4, color = [1, 1, 1, 1]) {
        return new Obj3D({
            geo: new Geo({
                data: {
                    'a_Position': { size: 3, array: new Float32Array(vertices) },
                    'a_Normal': { size: 3, array: new Float32Array(normals) },
                },
                element: { array: new Uint16Array(elements) }
            }),
            mat: new Mat({
                program: 'light',
                mode: ['TRIANGLES'],
                data: {
                    'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                    'u_ModelM4': { type: 'uniformMatrix4fv', value: mm4.elements },
                    'u_Color': { type: 'uniform4fv', value: color },
                    'u_Kd': { type: 'uniform1f', value: Kd },
                    'u_Ks': { type: 'uniform1f', value: Ks },
                    'u_Ka': { type: 'uniform1f', value: Ka },
                    'u_LightIntensity': { type: 'uniform1f', value: lightIntensity },
                    'u_LightDist': { type: 'uniform2fv', value: lightDist },
                    'u_LightFov': { type: 'uniform2fv', value: lightFov },
                    'u_LightEye': { type: 'uniform3fv', value: eye },
                    'u_LightLookAt': { type: 'uniform3fv', value: lookAt },
                    'u_LightPvM4': { type: 'uniformMatrix4fv', value: lightPvM4.elements },
                },
                maps: {
                    'u_ShadowMap': { texture: texture }
                }
            })
        });
    }
    function createDepth(vertices, elements, normals, mm4, color = [1, 1, 1, 1]) {
        return new Obj3D({
            geo: new Geo({
                data: {
                    'a_Position': { size: 3, array: new Float32Array(vertices) },
                },
                element: { array: new Uint16Array(elements) }
            }),
            mat: new Mat({
                program: 'depth',
                mode: ['TRIANGLES'],
                data: {
                    'u_PvM4': { type: 'uniformMatrix4fv', value: pvM4.elements },
                    'u_ModelM4': { type: 'uniformMatrix4fv', value: mm4.elements },
                    'u_Color': { type: 'uniform4fv', value: color },
                }
            })
        });
    }
    function createTexture(vertices, elements, mm4) {
        return new Obj3D({
            geo: new Geo({
                data: {
                    'a_Position': { size: 3, array: new Float32Array(vertices) },
                },
                element: { array: new Uint16Array(elements) }
            }),
            mat: new Mat({
                program: 'projection',
                mode: ['TRIANGLES'],
                data: {
                    'u_LightPvM4': { type: 'uniformMatrix4fv', value: lightPvM4.elements },
                    'u_ModelM4': { type: 'uniformMatrix4fv', value: mm4.elements },
                }
            })
        });
    }

    (function ani() {
        draw();
        requestAnimationFrame(ani)
    })();

    function draw() {

        light.position.copy(eye);
        light.lookAt(lookAt);
        light.updateMatrixWorld(true);
        lightPvM4.copy(light.projectionMatrix.clone().multiply(light.matrixWorldInverse));

        frame.draw();
        scene.draw();
    }
</script>

</html>